﻿using SqlSugar;
using Yitter.IdGenerator;
using Digitalmes.Domain.Repositories;

namespace Digitalmes.Infrastructure.Data;

/// <summary>
/// SqlSugar 设置。
/// </summary>
internal static class SqlSugarSetup
{
    /// <summary>
    /// 注册基于 SqlSugar 配置的服务
    /// </summary>
    /// <param name="services"></param>
    /// <param name="hostBuilderContext"></param>
    /// <param name="name">数据库连接的配置名称</param>
    public static void AddSqlSugarSetup(this IServiceCollection services, HostBuilderContext hostBuilderContext,
        string name = "DbConnection")
    {
        // 自定义 SqlSugar 雪花ID算法
        SnowFlakeSingle.WorkId = 1;
        StaticConfig.CustomSnowFlakeFunc = YitIdHelper.NextId;

        var dbOptions = hostBuilderContext.Configuration.GetSection(name).Get<DbConnectionOptions>();
        dbOptions?.ConnectionConfigs?.ForEach(SetDbConfig);

        SqlSugarScope CreateSqlSugarFunc(IServiceProvider sp) => new(dbOptions?.ConnectionConfigs.OfType<ConnectionConfig>().ToList(),
            db => dbOptions?.ConnectionConfigs.ForEach(config =>
            {
                var dbProvider = db.GetConnectionScope(config.ConfigId);
                SetDbAop(sp, dbProvider, hostBuilderContext.HostingEnvironment);
            }));

        services.AddSingleton<ISqlSugarClient>((Func<IServiceProvider, SqlSugarScope>)CreateSqlSugarFunc); // 单例注册
        services.AddScoped(typeof(IRepository<>), typeof(SqlSugarRepository<>)); // 仓储注册

        // 注册工作单元。注意，工作单元数据不能跨 DB。
        services.AddTransient<IUnitOfWorkManage, UnitOfWorkManager>();
    }

    /// <summary>
    /// 配置连接属性
    /// </summary>
    /// <param name="config"></param>
    public static void SetDbConfig(DbConnectionConfig config)
    {
        // 若 ConfigId 没有设置，会使用默认数据库标识。
        // 注：配置连接中的第一个不需要配置，其他的必须配置不同值。
        config.ConfigId ??= SqlSugarConst.ConfigId;
        config.ConfigureExternalServices = new ConfigureExternalServices
        {
            EntityNameService = (type, entity) => // 处理表
            {
                entity.IsDisabledDelete = true; // 禁止删除非 sqlsugar 创建的列
                // 只处理设置了[SugarTable]特性的表
                if (!type.IsDefined(typeof(SugarTable), false))
                    return;

                if (config.EnableUnderLine && !entity.DbTableName.Contains('_'))
                    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
            },
            EntityService = (type, column) => // 处理列
            {
                // 只处理设置了[SugarColumn]特性的列
                if (!type.IsDefined(typeof(SugarColumn), false))
                    return;

                if (new NullabilityInfoContext().Create(type).WriteState is NullabilityState.Nullable)
                    column.IsNullable = true;

                if (config.EnableUnderLine && !column.IsIgnore && !column.DbColumnName.Contains('_'))
                    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName); // 驼峰转下划线

                if (config.DbType == DbType.Oracle)
                {
                    if (type.PropertyType == typeof(long) || type.PropertyType == typeof(long?))
                        column.DataType = "number(18)";

                    if (type.PropertyType == typeof(bool) || type.PropertyType == typeof(bool?))
                        column.DataType = "number(1)";
                }
            },

            //DataInfoCacheService = new SugarMemoryCache(), // 设置二级缓存
        };

        config.InitKeyType = InitKeyType.Attribute;
        config.IsAutoCloseConnection = true;
        config.MoreSettings = new ConnMoreSettings
        {
            IsAutoRemoveDataCache = true,
            IsAutoDeleteQueryFilter = true, // 启用删除查询过滤器
            IsAutoUpdateQueryFilter = true, // 启用更新查询过滤器
            SqlServerCodeFirstNvarchar = true, // 采用Nvarchar
        };
    }

    /// <summary>
    /// 配置Aop
    /// </summary>
    /// <param name="db"></param>
    public static void SetDbAop(IServiceProvider sp, SqlSugarScopeProvider db, IHostEnvironment env)
    {
        var config = db.CurrentConnectionConfig;

        // 设置超时时间
        db.Ado.CommandTimeOut = 30;

        // 打印SQL语句
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            // 仅开发环境显示
            if (env.IsDevelopment())
            {
                var dateTime = sp.GetRequiredService<TimeProvider>().GetLocalNow();

                var originColor = Console.ForegroundColor;
                if (sql.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
                    Console.ForegroundColor = ConsoleColor.Green;
                else if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase)
                || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase))
                    Console.ForegroundColor = ConsoleColor.Yellow;
                else if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase))
                    Console.ForegroundColor = ConsoleColor.Red;

                var sqlStr = UtilMethods.GetSqlString(config.DbType, sql, pars);
                Console.WriteLine($"[{dateTime:yyyy-MM-dd HH:mm:ss.fff} —— SQL Executing] {Environment.NewLine}{sqlStr}{Environment.NewLine}");
                Console.ForegroundColor = originColor;
            }
        };

        db.Aop.OnError = ex =>
        {
            if (ex.Parametres == null) return;

            var dateTime = sp.GetRequiredService<TimeProvider>().GetLocalNow();

            var originColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.DarkRed;
            var sqlStr = UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres);
            Console.WriteLine($"[{dateTime:yyyy-MM-dd HH:mm:ss.fff} —— SQL Error] {Environment.NewLine}{sqlStr}{Environment.NewLine}");
            Console.ForegroundColor = originColor;
        };

        // 数据审计
        db.Aop.DataExecuting = (oldValue, entityInfo) =>
        {
            if (entityInfo.OperationType == DataFilterType.InsertByObject)
            {
                // 主键(long类型)且没有值的---赋值雪花Id
                if (entityInfo.EntityColumnInfo.IsPrimarykey 
                    && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
                {
                    var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                    if (id == null || (long)id == 0)
                        entityInfo.SetValue(SnowFlakeSingle.Instance.NextId());
                }

                if (entityInfo.EntityValue is ICreationAuditedEntity)
                {
                    if (entityInfo.PropertyName == nameof(ICreationAuditedEntity.CreatedTime))
                    {
                        var dateTime = sp.GetRequiredService<TimeProvider>().GetLocalNow();
                        entityInfo.SetValue(dateTime.DateTime);
                    }

                    if (entityInfo.PropertyName == nameof(ICreationAuditedEntity.CreatedBy))
                    {
                        var currentUser = sp.GetKeyedService<ICurrentUser>(nameof(ICurrentUser));
                        if (!string.IsNullOrEmpty(currentUser?.UserName))
                        {
                            entityInfo.SetValue(currentUser.UserName);
                        }
                    }
                }
            }
            else if (entityInfo.OperationType == DataFilterType.UpdateByObject)
            {
                if (entityInfo.EntityValue is IUpdationAuditedEntity)
                {
                    if (entityInfo.PropertyName == nameof(IUpdationAuditedEntity.LastModifiedTime))
                    {
                        var dateTime = sp.GetRequiredService<TimeProvider>().GetLocalNow();
                        entityInfo.SetValue(dateTime.DateTime);
                    }

                    if (entityInfo.PropertyName == nameof(IUpdationAuditedEntity.LastModifiedBy))
                    {
                        var currentUser = sp.GetKeyedService<ICurrentUser>(nameof(ICurrentUser));
                        if (!string.IsNullOrEmpty(currentUser?.UserName))
                        {
                            entityInfo.SetValue(currentUser.UserName);
                        }
                    }
                }
            }
        };

        // 配置实体假删除过滤器
        db.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
    }
}
